﻿using System;
using System.Reflection;
using devtm.Aop.Behavior;
using devtm.Aop.Copy;
using devtm.Aop.Emit;
using devtm.Aop.Event;
using devtm.Aop.Extensions;
using Mono.Cecil;
using Mono.Cecil.Cil;


namespace devtm.Aop.Convert
{
    

    public class Converter
    {


        private ModuleDefinition _module;
        private TypeDefinition _Interceptor;


        public Converter(ModuleDefinition module)
        {
            _module = module;
            _Interceptor = Converter.GetInterceptorType(_module, "Intercept");            
        }

        public Converter(ModuleDefinition module, TypeDefinition interceptor)
        {
            _module = module;
            this._Interceptor = interceptor;
        }


        /// <summary>
        /// Generates the specified method.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <param name="lst">The LST.</param>
        public void ReplaceMethod(MethodDefinition interceptedMethod, InterceptorContainer box)
        {


            string name = String.Format(InterceptEventArgsHelper.MaskBaseExtendedName, interceptedMethod.Name);

            #region Wrapper

            CopyMethod cm = new CopyMethod(interceptedMethod);
            MethodDefinition newMethod = cm.CloneMethod(
                interceptedMethod.DeclaringType, 
                interceptedMethod.DeclaringType, 
                true,
                name);            

            interceptedMethod.MoveMethodBody(newMethod);

            #endregion

            using (CilWorker gen = new CilWorker(interceptedMethod.Body))
            {
                GenerateContentMethod(box, newMethod, gen);
            }
        }

        // MethodDefinition interceptedMethod, 
        public void GenerateContentMethod(InterceptorContainer box, MethodDefinition newMethod, CilWorker gen)
        {



            #region Init

            MethodReference method1 = _module.Import(typeof(InterceptAttribute).GetMethod("Get", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(Object[]) }, null));
            TypeReference typeObj = _module.Import(typeof(Object));
            MethodReference methodset_Exception = _module.Import(typeof(devtm.Aop.Event.InterceptEventArgs).GetMethod("set_Exception", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(Exception) }, null));
            MethodReference methodset_Return = _module.Import(typeof(devtm.Aop.Event.InterceptEventArgs).GetMethod("set_Return", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(Exception) }, null));
            MethodReference methodget_Return = _module.Import(typeof(devtm.Aop.Event.InterceptEventArgs).GetMethod("get_Return", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { }, null));
            MethodReference methodget_PolicyEntry = _module.Import(typeof(devtm.Aop.Event.InterceptEventArgs).GetMethod("get_PolicyEntry", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { }, null));
            MethodReference methodAddProperty = _module.Import(typeof(devtm.Aop.Event.InterceptEventArgs).GetMethod("AddProperty", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(Int32), typeof(String), typeof(Object) }, null));


            // on récupère la position de la première brace
            SequencePoint sequenceBegin = newMethod.Body.Instructions[0].SequencePoint;

            // on récupère la position de la dernière brace
            SequencePoint sequenceEnd = null;
            for (int i = newMethod.Body.Instructions.Count - 1; i > 0; i--)
                if (newMethod.Body.Instructions[i].SequencePoint != null)
                {
                    sequenceEnd = newMethod.Body.Instructions[i].SequencePoint;
                    break;
                }

            #endregion


            #region Generate


            VariableDefinition var1 = null;
            VariableDefinition varTestBool = gen.Declare(_module.Import(typeof(bool)), "varTestBool");
            if (!gen.IsVoid())
            {
                var1 = gen.Declare(gen.Body.Method.ReturnType, "retour");
            }

            Instruction instructionIl = gen.Emit(OpCodes.Nop);
            sequenceBegin.CopyTo(instructionIl);


            // Preparing locals
            VariableDefinition arg = gen.Declare(_module.Import(typeof(devtm.Aop.Event.InterceptEventArgs)), "args");
            VariableDefinition tb = gen.Declare(_module.Import(typeof(Object[])));
            var Arg = new EmitVariableReferenceExpression(arg);
            var Tb = new EmitVariableReferenceExpression(tb);

            #region Before Try

            // Writing body
            gen.LocateFirst();

            // Creation de la variable Arg
            CreateArgument(method1, typeObj, gen, Tb, Arg);

            box.WriteMethodBeforeCall(gen, Arg, methodAddProperty, _Interceptor);

            #endregion


            Label l4 = gen.DefineLabel();

            // Start Try
            var Try = gen.CreateTryFinallyBloc();

            TryCatchCilWorker Catch = Try.CreateTryCatchBloc(Try.TryStart, typeof(Exception));

            #region Finally Handler Content

            Try.Locate(Try.HandlerStart);
            box.WriteMethodAfterCall(Try, Arg, methodAddProperty, _Interceptor);


            if (var1 != null)
            {
                Label l8 = gen.DefineLabel();

                // Intercept return Value
                Try.GetStockLocation(arg);                                              //L_0026: ldloc.1 
                Try.Insert(OpCodes.Callvirt, methodget_PolicyEntry);                    //L_0027: callvirt instance valuetype [devtm.Aop]devtm.Aop.Event.PolicyEntryEnum [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_PolicyEntry()
                Try.Insert(OpCodes.Ldc_I4_4);                                           //L_002c: ldc.i4.2 
                Try.Insert(OpCodes.Ceq);                                                //L_002d: ceq 
                Try.Insert(OpCodes.Ldc_I4_0);                                           //L_002f: ldc.i4.0 
                Try.Insert(OpCodes.Ceq);                                                //L_0030: ceq 
                Try.Insert(OpCodes.Stloc_S, varTestBool);                               //L_0032: stloc.s varTestBool
                Try.Insert(OpCodes.Ldloc_S, varTestBool);                               //L_0034: ldloc.s varTestBool
                Try.Insert(OpCodes.Brtrue_S, l8);                                       //L_0036: brtrue.s L_0066
                Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
                Try.Insert(OpCodes.Ldloc_S, arg);                                            //L_0039: ldloc.1 
                Try.Insert(OpCodes.Callvirt, methodget_Return);                         //L_003a: callvirt instance object [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_Return()
                Try.Insert(OpCodes.Isinst, gen.Body.Method.ReturnType);                             //L_003f: isinst string
                Try.SetStockLocation(var1);                                             //L_0063: stloc.3 
                Try.Insert(OpCodes.Br_S, l4);
                Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
                Try.MarkLabel(l8);
            }


            Label l7 = gen.DefineLabel();

            // throwed
            Try.GetStockLocation(arg);                                              //L_0026: ldloc.1 
            Try.Insert(OpCodes.Callvirt, methodget_PolicyEntry);                    //L_0027: callvirt instance valuetype [devtm.Aop]devtm.Aop.Event.PolicyEntryEnum [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_PolicyEntry()
            Try.Insert(OpCodes.Ldc_I4_3);                                           //L_002c: ldc.i4.1 
            Try.Insert(OpCodes.Ceq);                                                //L_002d: ceq 
            Try.Insert(OpCodes.Ldc_I4_0);                                           //L_002f: ldc.i4.0 
            Try.Insert(OpCodes.Ceq);                                                //L_0030: ceq 
            Try.Insert(OpCodes.Stloc_S, varTestBool);                               //L_0032: stloc.s varTestBool
            Try.Insert(OpCodes.Ldloc_S, varTestBool);                               //L_0034: ldloc.s varTestBool
            Try.Insert(OpCodes.Brtrue_S, l7);                                       //L_0036: brtrue.s L_0045
            Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
            Try.Insert(OpCodes.Ldloc_1);                                            //L_0039: ldloc.1 
            Try.Insert(OpCodes.Callvirt, methodget_Return);                         //L_003a: callvirt instance object [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_Return()
            Try.Insert(OpCodes.Isinst, _module.Import(typeof(System.Exception)));   //L_003f: isinst [mscorlib]System.Exception
            Try.Insert(OpCodes.Throw);                                              //L_0044: throw 
            Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
            Try.MarkLabel(l7);





            // On positionne le curseur de debogage sur la brace de fin
            sequenceEnd.CopyTo(Try.HandlerStart);

            #endregion


            #region Catch Exception

            if (box.ContainsCatch)
            {

                Catch.Locate(Catch.HandlerStart);

                Instruction il2 = Catch.GetStockLocation(Arg);

                // On positionne le curseur de debogage sur la brace de fin.
                sequenceEnd.CopyTo(Catch.HandlerStart);

                Catch.GetStockLocation(Catch.Variable);
                Catch.Insert(OpCodes.Callvirt, methodset_Exception);
                //WriteMethodIntercept(lst, Catch, "devtm.Aop.Behevior.InterceptExceptionAttribute", "OnException", Arg, methodAddProperty);
                box.WriteMethodCatchCall(Catch, Arg, methodAddProperty, _Interceptor);

            }

            gen.Body.ExceptionHandlers.Add(Catch.ExceptionHandler);
            gen.Body.ExceptionHandlers.Add(Try.ExceptionHandler);


            #endregion

            Try.Locate(Try.TryStart);

            if (box.ContainsEntry)
            {

                Label l2 = gen.DefineLabel();

                // throwed
                Try.GetStockLocation(arg);                                              //L_0026: ldloc.1 
                Try.Insert(OpCodes.Callvirt, methodget_PolicyEntry);                    //L_0027: callvirt instance valuetype [devtm.Aop]devtm.Aop.Event.PolicyEntryEnum [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_PolicyEntry()
                Try.Insert(OpCodes.Ldc_I4_1);                                           //L_002c: ldc.i4.1 
                Try.Insert(OpCodes.Ceq);                                                //L_002d: ceq 
                Try.Insert(OpCodes.Ldc_I4_0);                                           //L_002f: ldc.i4.0 
                Try.Insert(OpCodes.Ceq);                                                //L_0030: ceq 
                Try.Insert(OpCodes.Stloc_S, varTestBool);                               //L_0032: stloc.s varTestBool
                Try.Insert(OpCodes.Ldloc_S, varTestBool);                               //L_0034: ldloc.s varTestBool
                Try.Insert(OpCodes.Brtrue_S, l2);                                       //L_0036: brtrue.s L_0045
                Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
                Try.Insert(OpCodes.Ldloc_1);                                            //L_0039: ldloc.1 
                Try.Insert(OpCodes.Callvirt, methodget_Return);                         //L_003a: callvirt instance object [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_Return()
                Try.Insert(OpCodes.Isinst, _module.Import(typeof(System.Exception)));   //L_003f: isinst [mscorlib]System.Exception
                Try.Insert(OpCodes.Throw);                                              //L_0044: throw 
                Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
                Try.MarkLabel(l2);


                if (var1 != null)
                {

                    Label l3 = gen.DefineLabel();

                    // Intercept return Value
                    Try.GetStockLocation(arg);                                              //L_0026: ldloc.1 
                    Try.Insert(OpCodes.Callvirt, methodget_PolicyEntry);                    //L_0027: callvirt instance valuetype [devtm.Aop]devtm.Aop.Event.PolicyEntryEnum [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_PolicyEntry()
                    Try.Insert(OpCodes.Ldc_I4_2);                                           //L_002c: ldc.i4.2 
                    Try.Insert(OpCodes.Ceq);                                                //L_002d: ceq 
                    Try.Insert(OpCodes.Ldc_I4_0);                                           //L_002f: ldc.i4.0 
                    Try.Insert(OpCodes.Ceq);                                                //L_0030: ceq 
                    Try.Insert(OpCodes.Stloc_S, varTestBool);                               //L_0032: stloc.s varTestBool
                    Try.Insert(OpCodes.Ldloc_S, varTestBool);                               //L_0034: ldloc.s varTestBool
                    Try.Insert(OpCodes.Brtrue_S, l3);                                       //L_0036: brtrue.s L_0066
                    Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
                    Try.Insert(OpCodes.Ldloc_S, arg);                                            //L_0039: ldloc.1 
                    Try.Insert(OpCodes.Callvirt, methodget_Return);                         //L_003a: callvirt instance object [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::get_Return()
                    Try.Insert(OpCodes.Isinst, gen.Body.Method.ReturnType);                             //L_003f: isinst string
                    Try.SetStockLocation(var1);                                             //L_0063: stloc.3 
                    Try.Insert(OpCodes.Leave_S, l4);
                    Try.Insert(OpCodes.Nop);                                                //L_0038: nop 
                    Try.MarkLabel(l3);
                }
            }


            if (box.ContainsIntercept)
            {
                box.WriteMethodInterceptCall(Try, Arg, methodAddProperty, _Interceptor);
            }
            else
            {
                // Call la method reelle
                Try.Insert(OpCodes.Ldarg_0);                                //L_0001: ldarg.0                                                                             
                foreach (ParameterDefinition p1 in gen.Body.Method.Parameters)          //L_0002: ldarg.(x)
                    Try.WriteParameterArgument(p1);
                Try.Insert(OpCodes.Call, newMethod);                          //L_0004: call instance string ConsoleTest.Test.Class1::toto2Bis(string, int32)
            }


            // Si la method reelle n'est pas void on stock le resultat
            if (var1 != null)
            {
                Try.SetStockLocation(var1);                             //L_0009: stloc.0   (str)
                Try.GetStockLocation(arg);                              //L_0009: ldloc.1   (arg)
                Try.GetStockLocation(var1);                             //L_0009: ldloc.1   (arg)
                Try.Insert(OpCodes.Callvirt, methodset_Return);
                Label l1 = Try.DefineLabel();
            }


            // End
            if (var1 != null)
            {
                gen.Locate(gen.Last);
                gen.GetStockLocation(var1);
            }
            gen.MarkLabel(l4);
            gen.Emit(OpCodes.Ret);



            #endregion


        }



        #region Common

        public static void CreateArgument(MethodReference method1, TypeReference typeObj, CilWorker gen, EmitVariableReferenceExpression tb, EmitVariableReferenceExpression arg)
        {

            var cntParameters = gen.Body.Method.Parameters.Count;

            gen.Insert
            (
                tb.Set(new EmitArrayCreateExpression(typeObj, cntParameters))           // tb = new Object[2]
            );


            for (int i = 0; i < cntParameters; i++)
            {

                // tb[i] = (arg 1)
                gen.Insert
                (
                    new EmitArrayIndexerExpression                     // pointe un index de tableau
                    (
                       tb,                                             // reference la variable du tableau              //gen.Insert(OpCodes.Ldloc_1);
                       new EmitPrimitiveExpression(i)                  // pointe l'element 0                            //gen.Insert(OpCodes.Ldc_I4_0);
                    ).Set                                                                                               // Pousse arg 1
                    (new EmitArgumentReferenceExpression(i))           // on cherche l'argument '1' de la methode       //gen.Insert(OpCodes.Ldarg_1);                                                                                                                           
                );

            }


            gen.Insert
            (
                new EmitAssignStatement                           // pousse var tb
                (                                                 // gen.Insert(OpCodes.Ldloc_1);
                    arg,                                          // Call method
                    new EmitMethodReferenceExpression(method1)    // gen.Insert(OpCodes.Call, method1);                   
                      .Invoke(tb)                                 // pousse ver var arg
                )                                                 // gen.Insert(OpCodes.Stloc_1);
            );


        }

        public static TypeDefinition GetInterceptorType(ModuleDefinition module, string name)
        {

            TypeDefinition t = null;

            foreach (TypeDefinition item in module.Types)
            {

                if (item.Namespace == "Interceptor" && item.Name == name)
                {
                    t = item;
                    break;
                }

            }

            if (t == null)
            {

                t = new TypeDefinition
                    (
                        "Interceptor",
                        name,
                        Mono.Cecil.TypeAttributes.Public,
                        module.Import(typeof(Object))
                    );

                TypeReference voidType = module.Import(typeof(void));
                module.Types.Add(t);
            }

            return t;
        }

        #endregion

    }


}
